package belf.migrate.engine.ddl.function.mysql;

import belf.migrate.api.exception.APIErrorCode;
import belf.migrate.api.exception.CatalogException;
import belf.migrate.api.model.QualifiersModel;
import belf.migrate.api.table.schema.Catalog;
import belf.migrate.api.util.QuoteType;
import belf.migrate.engine.ddl.BaseDDLConvert;
import belf.migrate.engine.ddl.helper.mysql.MySql2DMProcedureListener;
import belf.migrate.engine.ddl.helper.mysql.MySqlAntlrBuilder;
import belf.migrate.plugin.mysql.antlr.MySqlLexer;
import belf.migrate.plugin.mysql.antlr.MySqlParser;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MySql2DMFunctionConvert extends BaseDDLConvert {

    public MySql2DMFunctionConvert(String objectName, String sql, Catalog sourceCatalog,
                                   Catalog sinkCatalog, String schema,
                                   QualifiersModel qualifiersModel) {
        super(objectName, sql, sourceCatalog, sinkCatalog, schema, qualifiersModel);
    }

    /**
     * 先获取以下信息：
     * <ul>
     * <li>节点"createFunction"下的函数名称、输入参数、返回类型，</li>
     * <li>BEGIN...END中间的declareVariable申明，</li>
     * <li>BEGIN...END中间的procedureSqlStatement语句</li>
     * </ul>
     * 再拼接转换后的SQL语句
     *
     * @return
     */
    @Override
    public String convert() {
        Pattern pattern = Pattern.compile("\\s+USING", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(sql);
        if (matcher.find()) {
            addWarning(new CatalogException(APIErrorCode.CONVERT_FUNCTION_ERROR, "达梦不支持USING子句，需要转换为类似的语句：ON TB2.ID=TB1.FK_ID"));
        }
        CharStream input = CharStreams.fromString(sql);

        // 创建词法分析器和解析器
        MySqlLexer lexer = new MySqlLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        MySqlParser parser = new MySqlParser(tokens);

        // 从解析器中获取起始规则并进行解析
        MySqlParser.RootContext tree = parser.root();
        MySqlFunctionVisitor visitor = new MySqlFunctionVisitor(this,
                schema,
                funcMapping,
                typeMapping);
        visitor.visit(tree);

        String returnType = visitor.getReturnType();
        if (StringUtils.isNotEmpty(returnType) && returnType.equalsIgnoreCase("tinyint(1)")) {
            returnType = "BOOL"; //mysql中函数返回值如果设置为tinyint(1)，达梦不支持tinyint(1)和BOOL值的转换，需要换为BOOL类型
        }
        String ddl = String.format("CREATE FUNCTION %s(%s) RETURN %s AS %s\nBEGIN \n%s \nEND;",
                visitor.getFunctionName(),
                String.join(",", buildParameters(visitor.getParameters())),
                returnType,
                buildDeclareVariables(visitor.getVariables()),
                String.join("\n", buildProcedureSqls(visitor.getProcedureSqls()))
        );
        return ddl;
//        return SqlFormatter.of(Dialect.PlSql).format(ddl); //达梦数据库的语法兼容Oracle
    }

    private List<String> buildParameters(List<MySqlParser.FunctionParameterContext> parameters) {
        List<String> list = new ArrayList<>();
        for (MySqlParser.FunctionParameterContext parameter : parameters) {
            String uid = parameter.uid().getText().trim();
            String dataType = parameter.dataType().getText().toUpperCase().trim();
            dataType = dataType.replace("UNSIGNED", "");
            if (typeMapping.containsKey(dataType)) {
                dataType = typeMapping.get(dataType).getTo();
            }
            list.add(uid + " " + dataType);
        }
        return list;
    }

    private String buildDeclareVariables(List<MySqlParser.DeclareVariableContext> variables) {
        String result = "";
        if (!variables.isEmpty()) {
            List<String> list = new ArrayList<>();
            for (MySqlParser.DeclareVariableContext variable : variables) {
                MySqlParser.UidListContext uidListContext = variable.uidList();
                List<String> uidNames = new ArrayList<>();
                for (MySqlParser.UidContext uidContext : uidListContext.uid()) {
                    uidNames.add(uidContext.getText().trim());
                }
                String dataType = variable.dataType().getText().trim();
                if (typeMapping.containsKey(dataType)) {
                    dataType = typeMapping.get(dataType).getTo();
                }
                list.add(String.join(",", uidNames) + " " + dataType + ";");
            }
            result = "\nDECLARE \n" + String.join("\n", list);
        }
        return result;
    }

    private List<String> buildProcedureSqls(List<MySqlParser.ProcedureSqlStatementContext> procedureSqls) {
        List<String> list = new ArrayList<>();
        for (MySqlParser.ProcedureSqlStatementContext procedureSql : procedureSqls) {
            StringBuilder sb = new StringBuilder();
            MySqlAntlrBuilder builder = new MySqlAntlrBuilder(this, sb, QuoteType.DOUBLE_QUOTE.getValue(), schema);
            MySql2DMProcedureListener listener = new MySql2DMProcedureListener(builder);
            ParseTreeWalker walker = new ParseTreeWalker();
            walker.walk(listener, procedureSql);
            list.add(listener.toSql());
        }
        return list;
    }
}
